Overview

Since we are shifting to a new dimension, we have to shift to a new target. Unless noted otherwise, the following labs will be targeting Urban Terror 4.3.4. This game is a first-person shooter based off the Quake engine. Like Wesnoth, this game is open-source and has no integrated anti-cheat. It also runs well on low-spec hardware. Unlike Wesnoth, the chocolatey package is broken. Due to this, the best way to install this is to download and run the installer from the site.

You will need to enable 3D acceleration in VirtualBox for the game to function. Depending on your computer's hardware, it may not be possible for your machine to run a 3D game inside a VM. In this case, you have a couple options. Some are better than others:

  1. Explore another hypervisor like VMWare or Hyper-V.
  2. Use another machine as a dedicated hacking computer and isolate it from your home network.
  3. Find another target game with even less requirements and follow along with the concepts of the labs.
  4. Partition your hard-drive and dual-boot. Even if you encrypt your personal drive, it is possible for malicious tools to access your personal data.
  5. Run the target and tools on your personal machine and hope that nothing malicious happens.

Identify

Our goal in this lab is to create a wallhack, a type of hack that allows us to see other players through walls. In this lab, we will not modify any of the graphics functions of the game. Instead, we will use the game's rendering logic and modify sections of the game's memory.

Understand

In 3D games, depth testing is used to determine when an item should be visible in the player's viewport. For example, if a player is behind a wall, depth testing will tell the rendering logic of the game to not draw the player. All wallhacks operate on the principle of disabling depth testing.

One way to do this is by hooking the graphics library of the game and disabling depth testing through library functions. We will cover this approach in the next lab. In this lab, we will rely on the game's built-in rendering logic to achieve our goal.

Games have to draw many dynamic objects, including players, weapons, and map assets like doors. These objects are normally referred to as entities. To simplify development and increase performance, games will often use the same function for drawing all of these entities. However, these entities often have different rendering considerations. A game may want to draw shadows on characters, but not on static entities like doors that can be opened. Games will often have structures for each entity and store these rendering considerations in the entity's structure. When the entity is rendered, the game will check this member and render the entity according to it.

For some entities, like puddles of water or glass, games will want to disable depth testing. As such, the render member in the entity class will have a disable depth testing value. If we can locate the function responsible for drawing entities and then modify all entities to contain this disabled depth testing value, players will appear through walls.

Target Setup

All games that are based off the Quake engine have a console. This console can be accessed by hitting the tilde (~) key while in-game. This console allows you to run commands, such as moving the player or changing a map. These commands typically start with a backslash(\) and can be auto-completed by hitting tab. Some helpful commands for our purposes are:

  • "\devmap abbey" - start the map "Abbey" with cheats enabled
  • "\g_gametype 0" - set the default game mode to deathmatch
  • "\bot_enable 1" - enable bots to join a game
  • "\reload" - restart the current map
  • "\addbot boa 1" - add a bot

In addition to these commands, we can also easily switch the game to a windowed mode by hitting Alt+Enter.

Locating Draw Entities

By exploring the commands available to us, we can find several drawing commands under "\r_":

Urban Terror Console Commands

The most interesting command to us is "r_drawentities". By setting this value to 0, entities are not drawn in the game, including our player:

Player not being drawn

We can assume the game's code looks something like:

  if(r_drawentities == 1) {
    draw_entities();    
  }

To find this code, we will use a similar method we used in previous labs. First, we will use Cheat Engine to find the address of the variable holding the "r_drawentities" value. We can switch the value of "r_drawentities" in the console from 0 to 1 to narrow this value down. Then, we can use a breakpoint on access in x64dbg to locate the code that accesses this value. The breakpoint should pop at the following code:

Urban Terror r_drawentities

We can see that the value of "r_drawentities" is loaded into ecx and then tested. Testing a register against itself compares that register's value to 0. If the value is equal to 0, the game jumps over the call at 0x52F717. This call is most likely responsible for drawing entities in the game. We can confirm this by NOP'ing out this call. When it is NOP'd, the game will not draw any entities.

Entities and Rendering

If we step inside the call at 0x52F717, we see the following code:

Urban Terror Entity Rendering Code

We can see in the second highlighted block that values are loaded into several registers and compared to certain values. If these values are equal, the game jumps to different locations and executes different rendering code. If we look closely, we can see that the registers are based off values of the address held in ebx. If we look up at the first highlighted block, we find the closest location in which ebx is set. We now know that at address 0x52D2FD, ebx contains what is most likely the current entity to render. If we set a breakpoint at this address and observe ebx's address in the dump, we see a chunk of data:

Urban Terror Entity Structure

Since this chunk of data is isolated from other data and in one continuous section, we can assume it represents a structure of some type. For example, it might look something like:

  struct entity {
    int type;
    int render_type;
    float location[3];
    ...
  }

To determine what location of the structure holds the render type, we must reverse this structure.

Reversing the Entity Structure

There are many ways to reverse an unknown structure in a game. One way is to build up a dataset of valid values and then make inferences based off these values. For example, if all the structures contain one member that constantly increases, we can assume that this member is being used as a counter of some type.

In this case, our goal is not to fully reverse the entity structure, but only reverse enough to find the render type variable. Since we have located the code responsible for drawing entities, we can set a breakpoint in that code and observe entity structures. Like we discussed in the last section, at address 0x52D2FD, ebx holds the address of the current entity to render.

One thing you will notice is that each time our breakpoint is hit, ebx contains a different value. While we could manually follow ebx in the dump each time the breakpoint is hit, a more convenient way is to use the "watch" feature of x64dbg. Adding an expression to the "watch" panel allows us to observe it independently of the dump. In this case, we can watch the expression "[ebx]" and always view the current value of the address in ebx.

To add a value to watch, open up the watch panel (near the dumps), right-click, and choose "Add":

Watch panel

In the modal the appears, type your expression. In this case, we want to start with just "[ebx]":

Watch panel

In addition, we want to also observe the first chunk of the entity structure. For now, we will assume all these values are 4 bytes long. Add watches for "[ebx+4]" through "[ebx+2C]". After you are finished, the watch panel should look like:

Watch panel

With all of this set up, disable your breakpoint and load into a map with water. The map "Abbey" has a fountain near the top-left of the map. Make sure you are facing the water and can see the ground beneath it.

Abbey pond location

With all of this set up, re-enable your breakpoint at 0x52D2FD and it should pop instantly. After observing the value of the watch panel, continue execution. After observing many iterations, you should start to notice some trends.

Watch panelWatch panelWatch panel

The value of [ebx] (red) always appears to be 0. The value of [ebx+4] (blue) appears to alternate between 0xD, 0x40, 0x82, 0x83. The value of [ebx+8] (white) appears to increase consistently, from 0x79 to 0x80 to 0x81, and on. The values highlighted in pink appear to alternate between seemingly random values and 0. Likewise, the values highlighted in yellow appear to be consistently tied to [ebx+8] but also appear random.

All this data can appear overwhelming, but we can make sense of it by eliminating values we do not care about. We know that we have at least three entities on the screen: our player model, our weapon, and the water. We can assume there are probably other entities, such as doors, as well. Since most of these entities share many similarities, we want to look for data that is relatively consistent between at least two entities. However, we also know that some of the entities should not share this value.

With this model, we can eliminate [ebx], since it is always 0. We can also eliminate [ebx+8], since it is unique for each entity. Both the values in pink and yellow appear unique for each object. This leaves us with [ebx+4] which alternates between 0xD, 0x40, 0x82, and 0x83. For now we will guess that this is our rendering value and investigate each value.

Modifying Rendering Value

If we set the value of [ebx+4] for each entity, we notice it will be overwritten the next time the draw entities function is called. It appears that the entity is being loaded into ebx from another location. Therefore, the easiest way for us to explore what we think is the rendering value is to hook the location 0x52D2FD and set [ebx+4] for every entity. We could create this codecave in x64dbg, but to make it easier for us to test multiple values, we will create our hook in a DLL.

We will use the same hooking structure discussed in the codecave lab. Our hook will be at 0x52D2FD, since we know ebx will contain the correct value at that point. Our hook itself will be relatively simple: we will save the registers, set the value of [ebx+4], restore the registers, and then execute the original move instruction:

  DWORD ret_address = 0x0052D303;

  __declspec(naked) void codecave() {
    __asm {
      pushad
      mov dword ptr ds:[ebx+4], ??? 
      popad
      mov dword ptr ds:[0x102AE98], ebx

      jmp ret_address
    }
  }

For our first value, let's start on the highest end and try 0x83:

  mov dword ptr ds:[ebx+4], 0x83

Once injected and back in game, you will notice that nothing appears to change. Likewise, if you try 0x40, you might notice some shadows seem different, but everything looks pretty similar. Next, let's try 0xD:

  mov dword ptr ds:[ebx+4], 0xD

Immediately you should notice your character's model now appears see-through in-front of the camera:

Disabled depth testing

This looks like a good sign that depth testing may have been disabled. Next, switch to third-person mode (cg_thirdperson) and add some bots. As you move around, you should notice that you can now see all bots through walls:

Disabled depth testing

With this, we have successfully set the rendering value for all entities to disable depth testing. We can also see that other entities appear through walls as well, such as guns and stairs. Our wallhack is a success! The full source code for this hack is available on github for comparison.

One improvement is to re-enable depth-testing for our player model so that first-person mode is not corrupted. To do this, we will need to identify the player structure and our current player, which will cover in a future lab.

Next Steps

In our next lab, we will create a wallhack by hooking the graphics library that the game uses.